<?php
namespace app\app\model;

use Exception;
use think\Model;
use app\app\model\App as AppModel;

use Exceptions\Notice\ParseErrorException;
use Exceptions\Notice\RequestErrorException;

/**
 * 发送通知的类
 */
class Notice extends Model {
    protected $pk = 'nid';
    protected $json = ['data', 'secret_data'];

    public const MAX_TRIES = 5;
    public const EXPIRE = 300; //5分钟超时，防止重放攻击

    /**
     * 发送通知（添加到发送队列）
     * @param string $event 事件名
     * @param array $data 要发送的数据
     * @param int $target 发送目标，-1：发送到所有开启通知的应用
     * @param array $secretData 要发送的加密数据（发送后立即从数据库中删除）
     */
    public static function send($event, $data = null, $target = -1, $secretData = null){
        $notice = new Notice();
        $notice->event = $event;
        $notice->time = time();
        if(is_array($data)){
            $notice->data = $data;
        }
        if(is_array($secretData)){
            $notice->secret_data = $secretData;
        }
        if(is_a($target, AppModel::class)){
            $notice->target = $target->appid;
        } else {
            $notice->target = $target;
        }
        $notice->save();
        return $notice->nid;
    }

    /**
     * 发送通知（同步发送）
     * @param string $event 事件名
     * @param array $data 要发送的数据
     * @param int $target 发送目标，-1：发送到所有开启通知的应用
     * @param array $secretData 要发送的加密数据（发送后立即从数据库中删除）
     * @return array 返回值
     */
    public static function sendSync($event, $data = null, $target = -1, $secretData = null){
        if(!is_a($target, AppModel::class)){
            $target = AppModel::get($target);
            if(!$target){
                throw new Exception('目标应用不存在');
            }
        }
        $data['event'] = $event;
        //加密数据
        $requestData = $target->encrypt(array_merge($data, $secretData));
        return self::sendRequest($target, $requestData);
    }

    /**
     * 发送所有异步通知（需要仔细改）
     * @return void
     */
    public static function render(){
        $notices = self::where(['succeed' => [0, -1]])->select();
        $sortedList = [];
        foreach($notices as $notice){ //开始发送通知
            $sortedList[$notice->target][] = $notice;
        }

        foreach($sortedList as $appid => $list){
            $requestData = [];
            $nids = [];
            foreach($list as $one){
                $t = [
                    'nid' => $one->nid,
                    'event' => $one->event,
                ];
                if($one->data){
                    $t['data'] = $one->data;
                }
                if($one->secret_data){
                    $t['data'] = array_merge($t['data'], $one->secret_data);
                }
                $requestData[] = $t;
                $nids[] = $one->nid;
            }

            $app = AppModel::get($appid);
            if(!$app){
                self::where(['nid' => $nids])->update([
                    'succeed' => -2,
                    'error' => '应用不存在',
                    'tries' => ['inc', 1],
                ]);
                continue;
            }
            
            try {
                $responses = self::request($app, $requestData);
                if(is_array($responses)){
                    $resNids = [];
                    foreach($responses as $response){ //写入返回值到数据库
                        $resNids[] = $response['nid'];
                        if(!isset($response['status'])){
                            $msg = '返回数据错误：' . json_encode($response);
                            self::where(['nid' => $nids])->fetchSql(true)->update([
                                'succeed' => -1,
                                'error' => $msg,
                                'tries' => ['inc', 1],
                            ]);
                            continue;
                        }
                        //待实现，处理status
                        switch($response['status']){
                            case 1: //成功
                                self::where(['nid' => $nids])->fetchSql(true)->update([
                                    'succeed' => 1,
                                ]);
                                break;
                            case 0: //方法不存在
                                $msg = '不存在执行此事件的接收器';
                                self::where(['nid' => $nids])->fetchSql(true)->update([
                                    'succeed' => -1,
                                    'error' => $msg,
                                    'tries' => ['inc', 1],
                                ]);
                                break;
                            case -1: //错误
                                $msg = isset($response['error']) ? $response['error'] : '服务器返回未知错误';
                                self::where(['nid' => $nids])->fetchSql(true)->update([
                                    'succeed' => -1,
                                    'error' => $msg,
                                    'tries' => ['inc', 1],
                                ]);
                                break;
                        }
                    }
                } else {
                    $msg = '返回数据为空';
                    self::where(['nid' => $nids])->fetchSql(true)->update([
                        'succeed' => -1,
                        'error' => $msg,
                        'tries' => ['inc', 1],
                    ]);
                }
            } catch(ParseErrorException $err){
                $msg = '无法解析的数据：' . $err->getMessage();
                self::where(['nid' => $nids])->fetchSql(true)->update([
                    'succeed' => -1,
                    'error' => $msg,
                    'tries' => ['inc', 1],
                ]);
            } catch(RequestErrorException $err){
                $msg = '请求错误 ' . $err->getCode() . '：' . $err->getMessage();
                self::where(['nid' => $nids])->fetchSql(true)->update([
                    'succeed' => -1,
                    'error' => $msg,
                    'tries' => ['inc', 1],
                ]);
            }
            self::where([['tries', '>=', self::MAX_TRIES]])->update(['succeed' => -2]); //将尝试发送超过3次的信息标记为不继续发送
        }
    }

    /**
     * 发送http request
     */
    private static function sendRequest($target, $data){
        $ch = curl_init();
        $opt = [
            CURLOPT_URL => $target->api_url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION =>true,
            CURLOPT_POST => true,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYSTATUS => false,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/octet-stream',
            ],
            CURLOPT_POSTFIELDS => $data,
        ];
        curl_setopt_array($ch, $opt);
        $result = curl_exec($ch);
        $resInfo = curl_getinfo($ch);
        $httpCode = $resInfo['CURLINFO_HTTP_CODE'];
        $contentType = $resInfo['CURLINFO_CONTENT_TYPE'];

        $canParse = true;
        if($contentType === 'application/octet-stream'){ //加密的返回值
            $resultData = $target->decrypt($result);
            if(!$resultData){
                $resultData = base64_encode($result);
                $canParse = false;
            }
        } elseif($contentType === 'application/json'){ //JSON返回值
            $resultData = json_decode($result);
            if(!$resultData){
                $resultData = $result;
                $canParse = false;
            }
        } else { //未知的返回值
            $resultData = $result;
            $canParse = false;
        }

        switch($httpCode){
            case 200: //执行正常
                if(!$canParse){
                    throw new ParseErrorException($result);
                }
                return $resultData;
                break;
            default: //其他错误
                throw new RequestErrorException($result, $httpCode);
                break;
        }
    }
}